﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using CoreEx.Common.Extensions;
using CoreEx.Common.Reflection;
using CoreEx.DynamicInvoker;
using LinFu.IoC.Configuration;

namespace CoreEx.Common.Repository.Implementation
{
    [Implements(typeof(IChangeSetFactory))]
    public class ChangeSetFactory : IChangeSetFactory
    {
        private readonly IDictionary<object, object> _clonedObjectMap = new Dictionary<object, object>();
        
        private IList<object> _clones = new List<object>();


        public IEnumerable<IObjectState> GetChangeSet(IEnumerable<IObjectState> trackedObjects)
        {
            IList<IObjectState> changeSet = new List<IObjectState>();
            foreach (var trackedObject in trackedObjects)
            {
                var clonedTarget = Clone(trackedObject.Target);
                var propertyNames =
                    trackedObject.Target.GetType().GetProperties().Where(
                        p => !p.PropertyType.IsEnumerable() && !p.PropertyType.IsSystemType()).Select(p => p.Name);

                IDictionary<string,object> propertyValues = new Dictionary<string, object>();
                foreach (var propertyName in propertyNames)
                {
                    propertyValues.Add(propertyName, Clone(trackedObject.OriginalValues[propertyName]));
                }

                IObjectState state = new ObjectState(clonedTarget, trackedObject.State, propertyValues);
                changeSet.Add(state);

            }
            return changeSet;
        }

        protected virtual T Clone<T>(T target) where T:class
        {
            if (target == null)
                return null;
            
            if (_clonedObjectMap.ContainsKey(target))
                return (T)_clonedObjectMap[target];
            
            var type = target.GetType();
            
            var clonedTarget = Activator.CreateInstance(type);

            //Map the primitive property types first
            var properties =
                type.GetProperties().Where(p => !p.PropertyType.IsEnumerable() && p.PropertyType.IsSystemType());

            properties.ForEach(p => p.DynamicInvoke(clonedTarget, p.DynamicInvoke(target)));


            properties =
                type.GetProperties().Where(p => !p.PropertyType.IsEnumerable() && !p.PropertyType.IsSystemType());

            _clonedObjectMap.Add(target,clonedTarget);

            foreach (var property in properties)
            {
                var value = property.DynamicInvoke(target);                
                property.DynamicInvoke(clonedTarget, Clone(value));
            }

            properties = type.GetProperties().Where(p => p.PropertyType.IsEnumerable());

            foreach (var property in properties)
            {
                property.GetBackingField().DynamicInvoke(clonedTarget, new object[]{null});
            }

            return (T)clonedTarget;
        }


    }
}
